package se.thoka.twt.client.item;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import se.thoka.twt.client.event.DatastoreChangedEvent;
import se.thoka.twt.client.event.DatastoreChangedHandler;
import se.thoka.twt.client.event.HasDatastoreChangedHandlers;
import se.thoka.twt.client.utils.ErrorReporter;
import se.thoka.twt.client.utils.TWTLogger;
import se.thoka.twt.shared.exceptions.IllegalOperationException;
import se.thoka.twt.shared.item.DatastoreChangeType;
import se.thoka.twt.shared.item.DatastoreOperation;
import se.thoka.twt.shared.item.ItemServiceAsync;
import se.thoka.twt.shared.utils.HasId;

import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.rpc.AsyncCallback;

public abstract class ClientDatastoreManager<T extends HasId, S extends ItemServiceAsync<T>> implements ItemServiceAsync<T>, HasDatastoreChangedHandlers<T> {
	
	protected final SimpleEventBus eventBus;
	
	private Set<DatastoreOperation> allowedOperations;
	
	protected ClientDatastoreManager() {
		eventBus = new SimpleEventBus();
		allowedOperations = new HashSet<DatastoreOperation>();
		
		// Allow all operations as default.
		for (DatastoreOperation operation : DatastoreOperation.values()) {
			allowedOperations.add(operation);
		}
	}
	
	@Override
	public HandlerRegistration addDatastoreChangedHandler(DatastoreChangedHandler<T> handler) {
		return eventBus.addHandler(DatastoreChangedEvent.TYPE, handler);
	}

	@Override
	public void delete(T entity, AsyncCallback<Void> callback) {
		assertOperationAllowed(DatastoreOperation.DELETE);
		getService().delete(entity, new DeleteCallback(entity, callback));
	}

	@Override
	public void insert(T entity, AsyncCallback<T> callback) throws IllegalOperationException {
		assertOperationAllowed(DatastoreOperation.INSERT);
		TWTLogger.log(getClass().getName() + "#insert(" + entity + ")");
		getService().insert(entity, new InsertCallback(callback));
	}

	@Override
	public void selectAll(AsyncCallback<Collection<T>> callback) throws IllegalOperationException {
		assertOperationAllowed(DatastoreOperation.SELECT_ALL);
		getService().selectAll(callback);
	}

	@Override
	public void selectById(String id, AsyncCallback<T> callback) throws IllegalOperationException {
		assertOperationAllowed(DatastoreOperation.SELECT_BY_ID);
		getService().selectById(id, callback);
	}

	@Override
	public void update(T entity, AsyncCallback<T> callback) throws IllegalOperationException {
		assertOperationAllowed(DatastoreOperation.UPDATE);
		getService().update(entity, new UpdateCallback(callback));
	}
	
	@Override
	public void fireEvent(GwtEvent<?> event) {
		eventBus.fireEvent(event);
	}
	
	public boolean isOperationAllowed(DatastoreOperation operation) {
		return allowedOperations.contains(operation);
	}
	
	protected abstract S getService();
	
	protected void onEntityInserted(T entity) {
		TWTLogger.log(getClass().getName() + "#onEntityInserted(" + entity + ") Fireing changed event");
		fireDatastoreChangedEvent(DatastoreChangeType.INSERT);
	}
	
	protected void onEntityUpdated(T entity) {
		TWTLogger.log(getClass().getName() + "#onEntityUpdated(" + entity + ") Fireing changed event");
		fireDatastoreChangedEvent(DatastoreChangeType.UPDATE);
	}
	
	protected void onEntityDeleted(T entity) {
		TWTLogger.log(getClass().getName() + "#onEntityDeleted(" + entity + ") Fireing changed event");
		fireDatastoreChangedEvent(DatastoreChangeType.DELETE);
	}
	
	protected void onEntityRefreshNeeded() {
		TWTLogger.log(getClass().getName() + "#onEntityRefreshNeeded() Fireing changed event");
		fireDatastoreChangedEvent(DatastoreChangeType.REFRESH);
	}
	
	protected void setOperationAllowance(DatastoreOperation operation, boolean allowed) {
		if (allowed) {
			allowedOperations.add(operation);
		} else {
			allowedOperations.remove(operation);
		}
	}
	
	protected void fireDatastoreChangedEvent(DatastoreChangeType changeType) {
		TWTLogger.log(getClass().getName() + "#fireDatastoreChangedEvent(" + changeType + ")");
		fireEvent(new DatastoreChangedEvent<T>(changeType));
	}
	
	protected void assertOperationAllowed(DatastoreOperation operation) {
		if (!isOperationAllowed(operation)) {
			throw new IllegalOperationException("Operation " + operation.name() + " not allowed.");
		}
	}
	
	protected class InsertCallback implements AsyncCallback<T> {
		private AsyncCallback<T> callback;
		public InsertCallback(AsyncCallback<T> callback) {
			this.callback = callback;
		}
		@Override
		public void onFailure(Throwable caught) {
			if (callback != null) {
				callback.onFailure(caught);
			} else {
				ErrorReporter.onRPCError(caught);
			}
		}
		@Override
		public void onSuccess(T result) {
			TWTLogger.log(getClass().getName() + "#onSuccess(" + result + ")");
			if (callback != null) {
				callback.onSuccess(result);
			}
			onEntityInserted(result);
		}
	}
	
	protected class DeleteCallback implements AsyncCallback<Void> {
		private AsyncCallback<Void> callback;
		private T entity;
		public DeleteCallback(T entity, AsyncCallback<Void> callback) {
			this.entity = entity;
			this.callback = callback;
		}
		@Override
		public void onFailure(Throwable caught) {
			if (callback != null) {
				callback.onFailure(caught);
			} else {
				ErrorReporter.onRPCError(caught);
			}
		}
		@Override
		public void onSuccess(Void result) {
			if (callback != null) {
				callback.onSuccess(result);
			}
			onEntityDeleted(entity);
		}
	}
	
	protected class UpdateCallback implements AsyncCallback<T> {
		private AsyncCallback<T> callback;
		public UpdateCallback(AsyncCallback<T> callback) {
			this.callback = callback;
		}
		@Override
		public void onFailure(Throwable caught) {
			if (callback != null) {
				callback.onFailure(caught);
			} else {
				ErrorReporter.onRPCError(caught);
			}
		}
		@Override
		public void onSuccess(T result) {
			if (callback != null) {
				callback.onSuccess(result);
			}
			onEntityUpdated(result);
		}
	}
	
	protected class RefreshCallback<R> implements AsyncCallback<R> {
		private AsyncCallback<R> callback;
		public RefreshCallback(AsyncCallback<R> callback) {
			this.callback = callback;
		}
		@Override
		public void onFailure(Throwable caught) {
			if (callback != null) {
				callback.onFailure(caught);
			} else {
				ErrorReporter.onRPCError(caught);
			}
		}
		@Override
		public void onSuccess(R result) {
			if (callback != null) {
				callback.onSuccess(result);
			}
			onEntityRefreshNeeded();
		}
	}
}
